在上一章Todolist with React (2),完成所有樣式設定後,現在就讓我們在 React 中加入 Redux,使用 React-redux 動態產生任務清單。
將狀態資料分成 Todo 任務資料和 Filter 篩選器資料,事先建立好 Store、Reducer、Action 的檔案。
src
├── ...
├── actions
├── ActionTypes.js
├── filter.js
├── todos.js
├── reducer
├── filter.js
├── index.js
├── todos.js
├── store.js
首先先處理渲染任務資料的部分,在 todos.js 定義出原始的任務資料 initialTasks
,包含任務的名稱和狀態,方便我們產生清單。
reducer/todos.js
const initialTasks = [
{ taskName: "task1", isCompleted: false },
{ taskName: "task2", isCompleted: true },
{ taskName: "task3", isCompleted: false },
];
繼續在同一份程式碼建立 Todo Reducer,State 參數的預設值為之前定義的 initialTasks
,目前尚未定義 Action,直接回傳當前原本的 State。
reducer/todos.js
import * as types from '../actions/ActionTypes';
export default function todos(state = initialTasks, action) {
switch (action.type) {
default:
return state;
}
}
因為我們會有兩份 Reducer,要用 combineReducers
將兩者合併,目前還沒做到 Filter Reducer,這邊就先填上 Todo Reducer。
reducer/index.js
import { combineReducers } from 'redux';
import todosReducer from './todos';
const todoApp = combineReducers({
todosReducer
});
export default todoApp;
再來建立 Store 引入 rootReducer,透過 <Provider>
的方式來傳遞 Store 讓網頁能夠讀取更新狀態。
src/store.js
import { createStore } from "redux";
import rootReducer from "./reducer/index";
const store = createStore(rootReducer);
export default store;
src/index.js
...
import { Provider } from "react-redux";
import store from "./store";
ReactDOM.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>,
...
);
將 Store 傳入 React Component 後,TaskList Component 就可以使用 useSelector
取得任務清單。原本我們是直接複製三個 <TaskItem />
產生三個任務,修改成利用 forEach
讀取 Store 的動態產生任務,並且把任務資料和編號 task={{ ...item, idx: index }}
傳入 <TaskItem />
:
components/TaskList.js
...
import { useSelector } from "react-redux";
function TaskList() {
const tasks = useSelector((store) => store.todosReducer);
const renderItems = () => {
let list = [];
tasks.forEach((item, index) => {
list.push(
<TaskItem key={item.taskName} task={{ ...item, idx: index }} />
);
});
return list;
};
return (
<Wrapper>
...
<TaskItemContainer>{renderItems()}</TaskItemContainer>
</Wrapper>
);
}
components/TaskItem.js
function TaskItem(props) {
return (
<Container>
<CheckBox
type="checkbox"
checked={props.task.isCompleted}
/>
<TaskName>{props.task.taskName}</TaskName>
<Button>Delete</Button>
</Container>
);
}
所有任務都能動態渲染後,再來處理任務新增 / 刪除的功能,在 ActionTypes.js 定義 ADD_TASK
和 DELETE_TASK
,這樣可以統一方便管理所有 Action。
actions/ActionTypes.js
export const ADD_TASK = 'ADD_TASK';
export const DELETE_TASK = 'DELETE_TASK';
Step1:建立 Action Creator 產生新增 Task 的動作 Function addTask
,並取得新增的任務名稱 taskName
。
actions/todos.js
import * as types from './ActionTypes';
export function addTask(taskName){
return {
type: types.ADD_TASK,
taskName
};
}
Step2:處理 Todo Reducer,讀取 action.type 為 ADD_TASK
時,儲存新任務的資料並回傳新的 State。
reducer/todos.js
import * as types from '../actions/ActionTypes';
const initialTasks = ...
export default function todos(state = initialTasks, action) {
switch (action.type) {
case types.ADD_TASK:
return [
...state,
{
taskName: action.taskName,
isCompleted: false,
},
];
default:...
}
}
Step3:完成 Action 和 Reducer 的設定後,在 AddTask Component 利用 useDispatch()
來調用 Action。點擊 <AddBtn />
後呼叫 handleClick()
,處理新增任務的程式。
components/AddTask.js
import { useDispatch } from "react-redux";
function AddTask() {
const dispatch = useDispatch();
const [newTask, setnewTask] = ...
const handleChange = ...
const handleClick = (event) => {
if(newTask === "") return; //檢查有沒有輸入任務名稱
dispatch(actions.addTask(newTask));
setnewTask("");
};
return (
<Wrapper>
...
<AddBtn onClick={() => handleClick()} >
<img src={addIcon} alt=""/>
</AddBtn>
</Wrapper>
);
}
Step1:刪除 Task 的部分也大同小異,第一步建立 Action Creator 產生刪除 Task 的動作 Function deleteTask
,並取得要刪除的任務索引值 idx
。
actions/todos.js
export function deleteTask(idx){
return {
type: types.DELETE_TASK,
idx
};
}
Step2:處理 Todo Reducer,讀取 action.type 為 DELETE_TASK
時,刪除該任務的資料並回傳的新 State。
actions/todos.js
export default function todos(state = initialTasks, action) {
switch (action.type) {
case types.ADD_TASK:...
case types.DELETE_TASK:
return [
...state.slice(0, action.idx),
...state.slice(action.idx + 1)
];
default:...
}
}
Step3:完成 Action 和 Reducer 的設定後,在 TaskItem Component 利用 useDispatch()
來調用 Action。點擊 <Button/>
後直接呼叫 deleteTask()
,傳入要刪除的任務索引值。
components/TaskItem.js
import { useDispatch } from "react-redux";
function TaskItem(props) {
const dispatch = useDispatch();
return (
<Container>
...
<Button onClick={() => dispatch(actions.deleteTask(props.task.idx))}>
Delete
</Button>
</Container>
);
}
今天完成了渲染任務清單、和任務新增刪除的動作,在下一篇文章,我們會繼續完成最後一個部分 — Filter 篩選器,那我們就明天見囉!
如果文章中有錯誤的地方,要麻煩各位大大不吝賜教;喜歡的話,也要記得幫我按讚訂閱喔❤️